/**
 * Caddie.java 21 juil. 07
 *
 * Copyright (c) 2007 windu.2b <jcaddie@windu2b.org>. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.windu2b.jcaddie.model;

import java.beans.*;
import java.io.*;
import java.util.*;

/**
 * @author windu.2b
 * 
 */
public class Caddie implements CaddieModel, Serializable
{
	/**
	 * 
	 */
	private static final long	                  serialVersionUID	= -8149117210666220429L;


	private List<CaddiePieceOfProduct>	          product;


	private transient List<Object>	              selectedItems;


	private transient List<CoursesListener>	      coursesListeners;


	private transient List<SelectionListener>	  selectionListeners;


	private transient PropertyChangeSupport	      propertyChangeSupport;


	private String	                              name;


	private transient boolean	                  modified;


	private CaddiePieceOfProduct.SortableProperty	productSortedProperty;


	private boolean	                              productDescendingSorted;



	public enum Property
	{
		NAME,

		MODIFIED,

		PRODUCT_SORTED_PROPERTY,

		PRODUCT_DESCENDING_SORTED,

		RECENT_CADDIES;
	}




	public Caddie()
	{
		this( new ArrayList<CaddiePieceOfProduct>() );
	}




	/**
	 * @param listeProduits la liste des produits à inclure dans le caddie
	 */
	public Caddie( List<CaddiePieceOfProduct> listeProduits )
	{
		this.product = new ArrayList<CaddiePieceOfProduct>( listeProduits );

		selectedItems = new ArrayList<Object>();
		coursesListeners = new ArrayList<CoursesListener>();
		selectionListeners = new ArrayList<SelectionListener>();
	}




	/**
	 * 
	 * @param property Le nom de la propriété à ajouter
	 * @param listener La propriété à ajouter à la liste des propriétés
	 */
	public void addPropertyChangeListener( String property,
	        PropertyChangeListener listener )
	{
		if ( propertyChangeSupport == null )
		{
			propertyChangeSupport = new PropertyChangeSupport( this );
		}

		propertyChangeSupport.addPropertyChangeListener( property, listener );
	}




	/**
	 * 
	 * @param property Le nom de la propriété à retirer
	 * @param listener La propriété à retirer à la liste des propriétés
	 */
	public void removePropertyChangeListener( String property,
	        PropertyChangeListener listener )
	{
		if ( propertyChangeSupport != null )
		{
			propertyChangeSupport.removePropertyChangeListener( property,
			        listener );
		}
	}




	/**
	 * 
	 * @return La liste des produits
	 */
	public List<CaddiePieceOfProduct> getProduct()
	{
		return Collections.unmodifiableList( product );
	}




	/**
	 * 
	 * @param index la position du produit recherché
	 * @return le produit placé à la position <code>index</code> dans le caddie
	 */
	public CaddiePieceOfProduct getProduct( int index )
	{
		return product.get( index );
	}




	/**
	 * @return le nombre de produits contenus dans le caddie
	 */
	public int size()
	{
		return product.size();
	}




	/**
	 * 
	 * 
	 * @see org.windu2b.jcaddie.model.CaddieModel#addPieceOfProduct(org.windu2b.jcaddie.model.CaddiePieceOfProduct)
	 */
	public void addPieceOfProduct( CaddiePieceOfProduct piece )
	{
		addPieceOfProduct( piece, product.size() );
	}




	/**
	 * Permet d'insérer un nouveau produit dans le caddie
	 * 
	 * @param piece
	 *            le produit à insérer
	 * @param index
	 *            la position d'insertion
	 */
	public void addPieceOfProduct( CaddiePieceOfProduct piece, int index )
	{
		product = new ArrayList<CaddiePieceOfProduct>( product );
		if ( !product.contains( piece ) )
		{
			product.add( index, piece );
			firePieceOfProductChanged( piece, index, CoursesEvent.Type.ADD );
		}
		else
		{
			piece = product.get( product.indexOf( piece ) );
			piece.addQuantite( 1 );
			firePieceOfProductChanged( piece, index,
			        CoursesEvent.Type.INCREMENT );
		}
	}




	public void deletePieceOfProduct( CaddiePieceOfProduct piece )
	{
		int index = product.indexOf( piece );
		if ( index != -1 )
		{
			product = new ArrayList<CaddiePieceOfProduct>( product );
			if ( product.get( index ).getQuantite() > 1 )
			{
				product.get( index ).removeQuantite( 1 );
				firePieceOfProductChanged( piece, index,
				        CoursesEvent.Type.DECREMENT );
			}
			else
			{
				deselectItem( piece );
				product.remove( index );
				firePieceOfProductChanged( piece, index,
				        CoursesEvent.Type.DELETE );
			}
		}
	}




	private void firePieceOfProductChanged( CaddiePieceOfProduct piece,
	        int index, CoursesEvent.Type eventType )
	{
		if ( !coursesListeners.isEmpty() )
		{
			CoursesEvent coursesEvent = new CoursesEvent( this, piece, index,
			        eventType );

			CoursesListener[] listeners = coursesListeners
			        .toArray( new CoursesListener[coursesListeners.size()] );

			for ( CoursesListener listener : listeners )
				listener.pieceOfCoursesChanged( coursesEvent );
		}
	}




	public void addSelectionListener( SelectionListener listener )
	{
		selectionListeners.add( listener );
	}




	public void removeSelectionListener( SelectionListener listener )
	{
		selectionListeners.remove( listener );
	}




	public List<Object> getSelectedItems()
	{
		return Collections.unmodifiableList( selectedItems );
	}




	public void setSelectedItems( List<? extends Object> selectedItems )
	{
		this.selectedItems = new ArrayList<Object>( selectedItems );
		if ( !selectionListeners.isEmpty() )
		{
			SelectionEvent selectionEvent = new SelectionEvent( this,
			        getSelectedItems() );
			SelectionListener[] listeners = selectionListeners
			        .toArray( new SelectionListener[selectionListeners.size()] );

			for ( SelectionListener listener : listeners )
			{
				listener.selectionChanged( selectionEvent );
			}
		}
	}




	private void deselectItem( Object item )
	{
		int pieceSelectionIndex = selectedItems.indexOf( item );
		if ( pieceSelectionIndex != -1 )
		{
			List<Object> selectedItems = new ArrayList<Object>(
			        getSelectedItems() );

			selectedItems.remove( pieceSelectionIndex );
			setSelectedItems( selectedItems );
		}
	}




	/**
	 * @see org.windu2b.jcaddie.model.CaddieModel#addCoursesListener(org.windu2b.jcaddie.model.CoursesListener)
	 */
	public void addCoursesListener( CoursesListener listener )
	{
		coursesListeners.add( listener );
	}




	/**
	 * @see org.windu2b.jcaddie.model.CaddieModel#removeCoursesListener(org.windu2b.jcaddie.model.CoursesListener)
	 */
	public void removeCoursesListener( CoursesListener listener )
	{
		coursesListeners.remove( listener );
	}




	/**
	 * @return the modified
	 */
	public boolean isModified()
	{
		return modified;
	}




	/**
	 * @param modified
	 *            the modified to set
	 */
	public void setModified( boolean modified )
	{
		if ( modified != this.modified )
		{
			this.modified = modified;
			if ( propertyChangeSupport != null )
			{
				propertyChangeSupport.firePropertyChange( "modified",
				        !modified, modified );
			}
		}
	}




	/**
	 * Returns the product property on which caddie is sorted or
	 * <code>null</code> if caddie product isn't sorted.
	 */
	public CaddiePieceOfProduct.SortableProperty getProductSortedProperty()
	{
		return productSortedProperty;
	}




	/**
	 * Sets the product property on which this caddie should be sorted and fires
	 * a <code>PropertyChangeEvent</code>.
	 */
	public void setProductSortedProperty(
	        CaddiePieceOfProduct.SortableProperty productSortedProperty )
	{
		if ( productSortedProperty != this.productSortedProperty
		        || ( productSortedProperty != null && !productSortedProperty
		                .equals( this.productSortedProperty ) ) )
		{
			CaddiePieceOfProduct.SortableProperty oldProductSortedProperty = this.productSortedProperty;
			this.productSortedProperty = productSortedProperty;
			if ( this.propertyChangeSupport != null )
			{
				this.propertyChangeSupport.firePropertyChange(
				        Property.PRODUCT_SORTED_PROPERTY.toString(),
				        oldProductSortedProperty, productSortedProperty );
			}
		}
	}




	/**
	 * Returns whether product is sorted in ascending or descending order.
	 */
	public boolean isProductDescendingSorted()
	{
		return this.productDescendingSorted;
	}




	/**
	 * Sets the product sort order on which caddie should be sorted and fires a
	 * <code>PropertyChangeEvent</code>.
	 */
	public void setProductDescendingSorted( boolean productDescendingSorted )
	{
		if ( productDescendingSorted != this.productDescendingSorted )
		{
			this.productDescendingSorted = productDescendingSorted;
			if ( this.propertyChangeSupport != null )
			{
				this.propertyChangeSupport.firePropertyChange(
				        Property.PRODUCT_DESCENDING_SORTED.toString(),
				        !productDescendingSorted, productDescendingSorted );
			}
		}
	}




	/**
	 * Adds the property change <code>listener</code> in parameter to this
	 * .
	 */
	public void addPropertyChangeListener( Property property,
	        PropertyChangeListener listener )
	{
		if ( this.propertyChangeSupport == null )
		{
			this.propertyChangeSupport = new PropertyChangeSupport( this );
		}
		this.propertyChangeSupport.addPropertyChangeListener( property
		        .toString(), listener );
	}




	/**
	 * Removes the property change <code>listener</code> in parameter from
	 * this .
	 */
	public void removePropertyChangeListener( Property property,
	        PropertyChangeListener listener )
	{
		if ( this.propertyChangeSupport != null )
		{
			this.propertyChangeSupport.removePropertyChangeListener( property
			        .toString(), listener );
		}
	}




	/**
	 * @return the name
	 */
	public String getName()
	{
		return name;
	}




	/**
	 * @param name
	 *            the name to set
	 */
	public void setName( String name )
	{
		if ( name != this.name || ( name != null && !name.equals( this.name ) ) )
		{
			String oldName = this.name;
			this.name = name;
			if ( propertyChangeSupport != null )
			{
				propertyChangeSupport
				        .firePropertyChange( "name", oldName, name );
			}
		}
	}




	/**
	 * Returns a sub list of <code>items</code> that contains only 
	 * product.
	 */
	public static List<CaddiePieceOfProduct> getProductSubList(
	        List<? extends Object> items )
	{
		return getSubList( items, CaddiePieceOfProduct.class );
	}




	@SuppressWarnings( "unchecked" )
	private static <T> List<T> getSubList( List<? extends Object> items,
	        Class<T> subListClass )
	{
		List<T> subList = new ArrayList<T>();
		for ( Object item : items )
		{
			if ( subListClass.isInstance( item ) )
			{
				subList.add( ( T ) item );
			}
		}
		return subList;
	}




	/**
	 * @param piece
	 * @param name
	 *            Updates the name of <code>piece</code>. Once the
	 *            <code>piece</code> is updated, product listeners added to
	 *            this caddie will receive a
	 *            {@link ProductListener#pieceOfProductChanged(ProductEvent) pieceOfProductChanged}
	 *            notification.
	 */
	public void setPieceOfProductName( CaddiePieceOfProduct piece, String name )
	{
		if ( ( piece.getName() == null && name != null )
		        || ( piece.getName() != null && !piece.getName().equals( name ) ) )
		{
			piece.setName( name );
			firePieceOfProductChanged( piece, product.indexOf( piece ),
			        CoursesEvent.Type.UPDATE );
		}
	}
}
